package com.lc.driver;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.webkit.MimeTypeMap;

import com.lc.obex.BluetoothObexTransport;
import com.lc.obex.BluetoothShare;
import com.lc.obex.ClientOperation;
import com.lc.obex.ClientSession;
import com.lc.obex.HeaderSet;
import com.lc.obex.ObexTransport;
import com.lc.obex.ResponseCodes;
import com.lc.protocol.IBaseDevice;
import com.lc.protocol.ITransmitHandler;
import com.lc.tool.Convert;

import org.json.JSONObject;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.UUID;

/**
 * Created by zhou-yb on 16/10/15.
 */

public class BluetoothTransmitHandler implements IBaseDevice, ITransmitHandler {

    protected static final UUID BLUETOOTH_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

    protected static String LogTitle = "BlueTooth";

    protected BluetoothDevice _btDevice = null;

    protected BluetoothSocket _btSocket = null;

    protected InputStream _inStream = null;

    protected OutputStream _outStream = null;
    private JSONObject osArg = new JSONObject();

    @Override
    public boolean Open(JSONObject sArg) {
        String devName = sArg.optString("Name");
        osArg = sArg;
        Close();
        Log.i(LogTitle, "Open Bluetooth:[" + devName + "]");

        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        Log.i(LogTitle, "getDefaultAdapter...");
        if (btAdapter == null) {
            Log.e(LogTitle, "BluetoothAdapter.getDefaultAdapter == null");
            return false;
        }
        Log.i(LogTitle, "getDefaultAdapter OK!");

        if (!btAdapter.isEnabled()) {
            if (!btAdapter.enable()) {
                Log.e(LogTitle, "Enable Bluetooth Failed.");
                return false;
            }
        }
        Log.i(LogTitle, "BluetoothAdapter getBondedDevices!");

        // 查找设备是否配对过
        Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
        if (pairedDevices.size() > 0) {
            for (BluetoothDevice device : pairedDevices) {
                String bondedName = device.getName();
                Log.i(LogTitle, "bonded :" + bondedName);
                bondedName = bondedName.toUpperCase();
                if (bondedName.contains(devName.toUpperCase())) {
                    Log.i(LogTitle, "getName OK!");
                    _btDevice = device;
                    break;
                }
            }
        } else {
            Log.e(LogTitle, "No Bonded device");
        }

        if (_btDevice == null) {
            Log.e(LogTitle, "_btDevice is null");
            return false;
        }

        Log.i(LogTitle, "_btSocket connect !");
        BluetoothSocket bs = null;
        try {
            Log.i(LogTitle, "_btDevice createRfcommSocketToServiceRecord");
            bs = _btDevice.createRfcommSocketToServiceRecord(BLUETOOTH_UUID);
        } catch (IOException e) {
            Log.e(LogTitle, "_btSocket createRfcommSocketToServiceRecord Failed", e);
            Field f;
            try {
                f = bs.getClass().getDeclaredField("mFdHandle");
                f.setAccessible(true);
                f.set(bs, 0x8000);
                bs.close();

                Thread.sleep(500);
            } catch (Exception e1) {
                Log.e(LogTitle, "mFdHandle", e1);
            }

            Method m;
            try {
                Log.i(LogTitle, "_btDevice createRfcommSocket");
                m = _btDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
                bs = (BluetoothSocket) m.invoke(_btDevice, 1);
            } catch (Exception e2) {
                Log.e(LogTitle, "createRfcommSocket", e2);
            }
        }
        if (bs != null) {
            Log.i(LogTitle, "Bluetooth Socket connect");
            try {
                bs.connect();
            } catch (IOException e) {
                bs = null;
                Log.e(LogTitle, "btSocket connect", e);
            }
        }
        _btSocket = bs;
        Log.i(LogTitle, "_btSocket != null : " + (_btSocket != null));
        return _btSocket != null;
    }

    @Override
    public boolean IsOpen() {
        if (_btDevice == null) {
            Log.e(LogTitle, "_btDevice is null");
        }
        if (_btSocket == null) {
            Log.e(LogTitle, "_btSocket is null");
        }
        return _btDevice != null && _btSocket != null;
    }

    @Override
    public void Close() {
        Log.i(LogTitle, "Close");
        try {
            if (_btSocket != null) {
                _btSocket.close();
            }
        } catch (IOException e) {
            Log.e(LogTitle, "close", e);
        }
        _btDevice = null;
        _btSocket = null;
        _inStream = null;
        _outStream = null;
    }

    @Override
    public boolean OnWrite(byte[] data, int datalen, int timeoutMs) {
        if (!IsOpen()) {
            Log.e(LogTitle, "设备未打开");
            return false;
        }
        if (_outStream == null) {
            try {
                _outStream = _btSocket.getOutputStream();
            } catch (IOException e) {
                Log.e(LogTitle, "socket get OutStream", e);
                return false;
            }
        }
        try {
            Log.i(LogTitle, "write length:" + datalen);
            Log.i(LogTitle, "socket write:" + Convert.HexToString(data, datalen));

            _outStream.write(data, 0, datalen);
            _outStream.flush();
        } catch (IOException e) {
            Log.e(LogTitle, "write Stream", e);
            return false;
        }
        return true;
    }

    @Override
    public boolean OnRead(byte[] data, int[] datalen, int timeoutMs) {
        if (!IsOpen()) {
            Log.e(LogTitle, "Device is closed");
            return false;
        }

        int rLen = 0;
        if (_inStream == null) {
            try {
                _inStream = _btSocket.getInputStream();
            } catch (IOException e) {
                Log.e(LogTitle, "socket getInputStream", e);
                e.printStackTrace();
                return false;
            }
        }

        try {
            if (_inStream.available() == 0) {
                return false;
            } else {
                rLen = _inStream.read(data);
                Log.i(LogTitle, "read length:" + rLen);
                Log.i(LogTitle, "socket read:" + Convert.HexToString(data, rLen));

                if (rLen > 0) {
                    datalen[0] = rLen;
                    return true;
                }
            }
        } catch (IOException e) {
            Log.e(LogTitle, "_inStream.read exception");
        }

        return false;
    }

    private IOppBack back = null;

    @RequiresApi(api = Build.VERSION_CODES.M)
    public boolean PullOppFile(String srcFile, String dstFile) {
        return PullOppFile(srcFile, dstFile, null);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public boolean PushOppFile(String srcFile, String dstFile) {
        return PushOppFile(srcFile, dstFile, null);
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public boolean PullOppFile(String srcFile, String dstFile, IOppBack back) {
        this.back = back;
        if (_btSocket == null || !_btSocket.isConnected()) return false;
        ClientSession cs = connect(1, new BluetoothObexTransport(_btSocket));
        if (null == cs)
            return false;
        int ret = receiveFile(srcFile, dstFile, cs);
        disconnect(cs);
        if (ret == BluetoothShare.STATUS_SUCCESS)
            return true;
        return false;
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    public boolean PushOppFile(String srcFile, String dstFile, IOppBack back) {
        this.back = back;
        if (_btSocket == null || !_btSocket.isConnected()) return false;
        ClientSession cs = connect(1, new BluetoothObexTransport(_btSocket));
        if (null == cs)
            return false;
        int ret = sendFile(srcFile, dstFile, cs);
        disconnect(cs);
        if (ret == BluetoothShare.STATUS_SUCCESS)
            return true;
        return false;
    }

    private void disconnect(ClientSession mCs) {
        if (mCs != null) {
            try {
                mCs.disconnect(null);
                mCs.close();
                Open(osArg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private int readFully(InputStream is, byte[] buffer, int size) throws IOException {
        int done = 0;
        while (done < size) {
            int got = is.read(buffer, done, size - done);
            if (got <= 0) break;
            done += got;
        }
        return done;
    }

    // url = file path or whatever suitable URL you want.
    private static String getMimeType(String url) {
        String type = null;
        String extension = MimeTypeMap.getFileExtensionFromUrl(url);
        if (extension != null) {
            MimeTypeMap mime = MimeTypeMap.getSingleton();
            type = mime.getMimeTypeFromExtension(extension);
        }
        return type;
    }

    //客户端
    private int sendFile(String srcFile, String dstFile, ClientSession mCs) {
        File fFile = new File(srcFile);
        long fsize = fFile.length();
        FileInputStream finputStream = null;
        int status = -1;
        HeaderSet request;
        request = new HeaderSet();
        request.setHeader(HeaderSet.NAME, dstFile);
        request.setHeader(HeaderSet.TYPE, getMimeType(dstFile));
        request.setHeader(HeaderSet.LENGTH, fsize);
        ClientOperation putOperation = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        BufferedInputStream bis = null;
        try {
            finputStream = new FileInputStream(fFile.getAbsoluteFile());
            if (finputStream == null) return BluetoothShare.STATUS_FILE_ERROR;
            putOperation = (ClientOperation) mCs.put(request);
            outputStream = putOperation.openOutputStream();
            inputStream = putOperation.openInputStream();
            int readLength = 0;
            long readbytesleft = 0;
            int outputBufferSize = putOperation.getMaxPacketSize();
            byte[] buffer = new byte[outputBufferSize];
            bis = new BufferedInputStream(finputStream, 0x4000);
            int position = 0;
            int responseCode = -1;
            if (!Thread.interrupted() && (position != fsize)) {
                readbytesleft = fsize - position;
                if (readbytesleft < outputBufferSize) {
                    outputBufferSize = (int) readbytesleft;
                }
                readLength = readFully(bis, buffer, outputBufferSize);
                outputStream.write(buffer, 0, readLength);
                outputStream.flush();
                position += readLength;

                boolean okToProceed = false;
                responseCode = putOperation.getResponseCode();
                if (responseCode == ResponseCodes.OBEX_HTTP_CONTINUE
                        || responseCode == ResponseCodes.OBEX_HTTP_OK) {
                    okToProceed = true;
                }
                while (!Thread.interrupted() && okToProceed && (position < fsize)) {
                    readbytesleft = fsize - position;
                    if (readbytesleft < outputBufferSize) {
                        outputBufferSize = (int) readbytesleft;
                    }
                    readLength = bis.read(buffer, 0, outputBufferSize);
                    outputStream.write(buffer, 0, readLength);
                    outputStream.flush();
                    responseCode = putOperation.getResponseCode();
                    if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE
                            && responseCode != ResponseCodes.OBEX_HTTP_OK) {
                        okToProceed = false;
                    } else {
                        position += readLength;
                        if (back != null) back.OppPosition(position);
                    }
                }
                if (!Thread.interrupted() && position == fsize) {
                    status = BluetoothShare.STATUS_SUCCESS;
                } else {
                    status = BluetoothShare.STATUS_CANCELED;
                    putOperation.abort();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (putOperation != null) {
                    putOperation.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
                status = BluetoothShare.STATUS_FORBIDDEN;
            }
        }
        return status;
    }

    private int receiveFile(String srcFile, String dstFile, ClientSession mCs) {
        File rFile = new File(dstFile);
        if (!rFile.getParentFile().exists()) {
            try {
                rFile.mkdirs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (!rFile.exists()) {
            try {
                rFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        FileOutputStream foutputStream = null;
        BufferedOutputStream bos = null;
        try {
            foutputStream = new FileOutputStream(rFile.getAbsoluteFile());
            bos = new BufferedOutputStream(foutputStream, 0x10000);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        if (foutputStream == null || bos == null) return BluetoothShare.STATUS_FILE_ERROR;
        int status = -1;
        HeaderSet request;
        request = new HeaderSet();
        request.setHeader(HeaderSet.NAME, srcFile);
        request.setHeader(HeaderSet.TYPE, getMimeType(dstFile));
        Long length = new Long(0x0FFFFFFF);
        request.setHeader(HeaderSet.LENGTH, length);
        ClientOperation getOperation = null;
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            getOperation = (ClientOperation) mCs.get(request);
            outputStream = getOperation.openOutputStream();
            inputStream = getOperation.openInputStream();
            int readLength = 0;
            int outputBufferSize = getOperation.getMaxPacketSize();
            byte[] buffer = new byte[outputBufferSize];
            int position = 0;
            int times = 0;
            boolean okToProceed = true;
            int responseCode = -1;
            while (!Thread.interrupted() && okToProceed) {
                // Log.e("read", "readLength start");
                if ((readLength = inputStream.read(buffer)) > 0) {
                    bos.write(buffer, 0, readLength);
                    bos.flush();
                    responseCode = getOperation.getResponseCode();
                    if (responseCode != ResponseCodes.OBEX_HTTP_CONTINUE
                            && responseCode != ResponseCodes.OBEX_HTTP_OK) {
                        okToProceed = false;
                    } else {
                        position += readLength;
                        if (back != null) back.OppPosition(position);
                    }
                }
                //Log.e("read", "readLength end:" + readLength);
                if (readLength == 0 && times < 10) {
                    times++;
                    Thread.sleep(50);
                    continue;
                }
                if (position > 0 && readLength <= 0) {
                    break;
                }
            }
            if (!Thread.interrupted() && position > 0 && readLength <= 0) {
                status = BluetoothShare.STATUS_SUCCESS;
            } else {
                status = BluetoothShare.STATUS_CANCELED;
                getOperation.abort();
                if (rFile != null) {
                    rFile.delete();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
                if (getOperation != null) {
                    getOperation.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
                status = BluetoothShare.STATUS_FORBIDDEN;
            }
        }
        return status;
    }

    private ClientSession connect(int numShares, ObexTransport mTransport) {
        ClientSession mCs;
        try {
            mCs = new ClientSession(mTransport);
            HeaderSet hs = new HeaderSet();
            hs.setHeader(HeaderSet.COUNT, (long) numShares);
            mCs.connect(hs);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return mCs;
    }

    public void setOppBack(IOppBack back) {
        this.back = back;
    }
}
